package com.coderside.fragment.Reference;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Fieldable;

import com.coderside.fragment.json.Person;
import com.coderside.fragment.json.Persons;

/**
 * Lucence工具类
 * 主要用来处理org.apache.lucene.document.Document和未知泛型的对象之间的转换
 * 通过buildDocument方法可以把一个未知泛型对象转换成为Document对象
 * 通过fromDocument方法可以把一个Document对象转换成泛型对象
 * 目前泛型对象的属性只支持java的基本数据类型、集合List类型、数组类型
 * List转换的规则:比如Persons.data[0].dateAttr Persons对象下面的属性data第一个元素的dateAttr属性
 * @author cheney
 *
 */
public class LucenceUtil {
	private static BeanUtilsBean beanUtil = BeanUtilsBean.getInstance();

	public static void main(String[] args) {
		Persons persions = new Persons();
		persions.setId("1");
		Person p = new Person();
		p.setDateAttr(new Date());
		p.setName("dog");
		List<Person> list = new ArrayList<Person>();
		List<String> array = new ArrayList<String>();
		array.add("array");
		list.add(p);
		persions.setData(list);
		persions.setArray(array);
		persions.setAge(10);
		persions.setStrarr(new String[] { "a", "b" });
		persions.setPerarr(new Person[] { p });
		Document doc = new Document();
		buildDocument(persions, doc);
		try {
			fromDocument(doc, Persons.class);
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println(doc);
	}

	@SuppressWarnings("unchecked")
	private static void parseCollection(Collection list, Document doc, String parent) throws IllegalAccessException,
			InvocationTargetException, NoSuchMethodException {
		int index = 0;
		for (Object e : list) {
			FNameUtil fname = new FNameUtil(parent, index);
			parseUnitItem(e, doc, fname);
			index++;
		}
	}

	@SuppressWarnings("unchecked")
	private static void parseArray(Object[] list, Document doc, String parent) throws IllegalAccessException,
			InvocationTargetException, NoSuchMethodException {
		int index = 0;
		for (Object e : list) {
			FNameUtil fname = new FNameUtil(parent, index);
			parseUnitItem(e, doc, fname);
			index++;
		}
	}

	/**
	 * 根据未知泛型对象build一个document对象
	 * @param <T>
	 * @param orig
	 * @param doc
	 */
	@SuppressWarnings("unchecked")
	public static <T> void buildDocument(T orig, Document doc) {
		PropertyDescriptor[] origDescriptors = beanUtil.getPropertyUtils().getPropertyDescriptors(orig);
		for (int i = 0; i < origDescriptors.length; i++) {
			String fieldName = origDescriptors[i].getName();
			if ("class".equals(fieldName)) {
				continue;
			}
			if (beanUtil.getPropertyUtils().isReadable(orig, fieldName)) {
				try {
					String parent = fieldName;
					Object value = beanUtil.getPropertyUtils().getSimpleProperty(orig, fieldName);
					if (value instanceof Collection) {
						//泛型对象的属性是集合
						Collection list = (Collection) value;
						parseCollection(list, doc, parent);
					} else if (value instanceof Date) {
						//泛型对象的属性是日期格式
						String dateStr = DateFormatUtils.ISO_DATE_FORMAT.format(value);
						Field f = new Field(fieldName, dateStr, Field.Store.YES, Field.Index.ANALYZED);
						doc.add(f);
					} else if (null != value && value.getClass().isArray()) {
						//泛型对象的属性是数组
						Object[] list = (Object[]) value;
						parseArray(list, doc, parent);
					} else {
						////泛型对象的属性是基本的数据类型，string在本类中也归为基本数据类型
						String fieldText = value == null ? "" : value.toString();
						Field f = new Field(fieldName, fieldText, Field.Store.YES, Field.Index.ANALYZED);
						doc.add(f);
					}

				} catch (NoSuchMethodException e) {
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * 
	 * @param orig
	 * @return
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 */
	private static boolean isBaseType(Class<?> orig) throws InstantiationException, IllegalAccessException {
		if (orig.isArray())
			return false;
		else if (orig == String.class) {
			return true;
		}
		return orig.isPrimitive();
	}

	/**
	 * 判断一个对象是否为基本数据类型
	 * @param orig
	 * @return 
	 */
	private static boolean isBaseType(Object orig) {

		if (orig instanceof String) {
			return true;
		} else if (orig instanceof Integer) {
			return true;
		} else if (orig instanceof Double) {
			return true;
		} else if (orig instanceof Long) {
			return true;
		} else if (orig instanceof Float) {
			return true;
		} else if (orig instanceof Byte) {
			return true;
		} else if (orig instanceof Character) {
			return true;
		} else if (orig instanceof Boolean) {
			return true;
		}
		return false;
	}

	/**
	 * 得到集合类中的单个元素，构建document对象
	 * @param orig
	 * @param doc
	 * @param parent
	 * @param index
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws NoSuchMethodException
	 */
	private static void parseUnitItem(Object orig, Document doc, FNameUtil fn) throws IllegalAccessException,
			InvocationTargetException, NoSuchMethodException {
		//先判断是否为基本数据类型
		if (isBaseType(orig)) {
			Field f = new Field(fn.getCollectionfn(null), (String) orig, Field.Store.YES, Field.Index.ANALYZED);
			doc.add(f);
			return;
		}
		PropertyDescriptor[] origDescriptors = beanUtil.getPropertyUtils().getPropertyDescriptors(orig);
		for (int i = 0; i < origDescriptors.length; i++) {
			String name = origDescriptors[i].getName();
			if ("class".equals(name)) {
				continue;
			}
			if (beanUtil.getPropertyUtils().isReadable(orig, name)) {
				Object value = beanUtil.getPropertyUtils().getSimpleProperty(orig, name);
				String fieldText = "";
				//判断是否为空
				if (null != value) {
					fieldText = value.toString();
					if (value instanceof Date) {
						fieldText = DateFormatUtils.ISO_DATE_FORMAT.format(value);
					}
				}
				Field f = new Field(fn.getCollectionfn(name), fieldText, Field.Store.YES, Field.Index.ANALYZED);
				doc.add(f);

			}
		}
	}

	/**
	 * 根据doc生成<T>对象
	 * 通过Method的getGenericParameterTypes可以知道一个属性的类型
	 * @param <T>
	 * @param doc
	 * @param claszz
	 * @return
	 * @throws Exception
	 */
	public static <T> T fromDocument(Document doc, Class<T> claszz) throws Exception {

		T dest = claszz.newInstance();
		PropertyDescriptor[] origDescriptors = beanUtil.getPropertyUtils().getPropertyDescriptors(claszz);
		for (int i = 0; i < origDescriptors.length; i++) {
			String fieldName = origDescriptors[i].getName();
			if ("class".equals(fieldName)) {
				continue;
			}
			//是否可以访问set方法 一般的方法都是public  所以一般都可以访问
			if (beanUtil.getPropertyUtils().isWriteable(dest, fieldName)) {
				Method writeMethod = beanUtil.getPropertyUtils().getWriteMethod(origDescriptors[i]);
				Type[] paramTypeList = writeMethod.getGenericParameterTypes();
				if (paramTypeList.length > 1) {
					throw new Exception("set Method 只能有一个参数类型");
				}
				Object value = null;
				Type type = paramTypeList[0];
				Class<?> clazz = null;
				//set方法的参数类型是集合类型Collection或者Map
				if (type instanceof ParameterizedType) {
					Type[] types = ((ParameterizedType) type).getActualTypeArguments();
					//clazz集合的泛型类型
					clazz = (Class<?>) types[0];
					value = getAttributes(doc, fieldName, clazz);
				} else if (((Class<?>) type).isArray()) { //数组
					clazz = (Class<?>) type;
					value = getArray(doc, fieldName, clazz.getComponentType());
				} else {
					value = doc.get(fieldName);
				}
				beanUtil.copyProperty(dest, fieldName, value);
			}
		}
		return dest;
	}

	private static boolean matcherReg(String regEx, String content) {
		Pattern p = Pattern.compile(regEx, Pattern.CASE_INSENSITIVE);
		Matcher m = p.matcher(content);
		return m.find();
	}

	@SuppressWarnings("unchecked")
	private static <T> T[] getArray(Document doc, String fieldName, Class<T> claszz) throws NumberFormatException,
			InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ParseException {
		boolean isPrimitive = isBaseType(claszz);
		T[] resArr = null;
		if (isPrimitive) {
			Collection<T> list = getBaseTypeFields(doc, fieldName, claszz);
			resArr = (T[]) Array.newInstance(claszz, list.size());
			list.toArray(resArr);
			return resArr;
		} else {
			Collection<T> attri = getReferFields(doc, fieldName, claszz);
			resArr = (T[]) Array.newInstance(claszz, attri.size());
			attri.toArray(resArr);
			return resArr;
		}
	}

	/**
	 * 生成泛型集合
	 * @param <T>
	 * @param doc
	 * @param fieldName
	 * @param claszz
	 * @return
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException 
	 * @throws NumberFormatException 
	 * @throws NoSuchMethodException 
	 * @throws ParseException 
	 */
	private static <T> Collection<T> getAttributes(Document doc, String fieldName, Class<T> claszz)
			throws InstantiationException, IllegalAccessException, NumberFormatException, InvocationTargetException,
			NoSuchMethodException, ParseException {
		boolean isPrimitive = isBaseType(claszz);
		if (isPrimitive) {
			return getBaseTypeFields(doc, fieldName, claszz);
		} else {
			Collection<T> attri = getReferFields(doc, fieldName, claszz);
			System.out.println(attri);
			return attri;
		}
	}

	/**
	 * 获取基本数据类型的Field
	 * String在这里算是基本数据类型，因为String不需要进行反射获取属性
	 * @param doc
	 * @param fieldName
	 * @return
	 * @throws ParseException 
	 */
	private static <T> Collection<T> getBaseTypeFields(Document doc, String fieldName, Class<T> claszz) throws ParseException {
		Collection<T> list = new ArrayList<T>();
		List<Fieldable> fields = doc.getFields();
		for (Fieldable fieldable : fields) {
			if (matcherReg(fieldName, fieldable.name())) {
				String field = doc.get(fieldable.name());
				list.add(convert(claszz, field));
			}
		}
		return list;
	}

	/**
	 * 获取泛型对象中非基本数据类型Field
	 * 需要通过反射获取属性都属于非基本数据类型
	 * 比如 ：data[0].dateAttr
	 * 就是非基本数据类型data中第一个元素的dateAttr属性
	 * Persons是目标泛型对象
	 * @param doc 文档对象
	 * @param fieldName 对象属性
	 * @param claszz 对象属性的Class类型
	 * @return
	 * @throws IllegalAccessException 
	 * @throws InstantiationException 
	 * @throws NumberFormatException 
	 * @throws InvocationTargetException 
	 * @throws NoSuchMethodException 
	 * @throws ParseException  
	 */
	private static <T> Collection<T> getReferFields(Document doc, String fieldName, Class<T> claszz)
			throws NumberFormatException, InstantiationException, IllegalAccessException, InvocationTargetException,
			NoSuchMethodException, ParseException {
		List<T> list = new ArrayList<T>();
		List<Fieldable> fields = doc.getFields();
		for (Fieldable fieldable : fields) {
			//document保存的字段属性名
			String fName = fieldable.name();
			//对象中的字段属性名和document保存的字段属性名：
			if (matcherReg(fieldName, fName)) {
				FNameUtil fn = new FNameUtil(fName);
				T instan = null;
				while (fn.getIndex() >= list.size()) {
					instan = claszz.newInstance();
					list.add(instan);
				}
				instan = list.get(fn.getIndex());
				Class<?> fieldClass = beanUtil.getPropertyUtils().getPropertyType(instan, fn.getParentField());
				Object attrivalue = convert(fieldClass, doc.get(fName));
				if (null != attrivalue) {
					beanUtil.copyProperty(instan, fn.getParentField(), attrivalue);
				}
				list.set(fn.getIndex(), instan);
			}
		}
		return list;
	}



	/**
	 * 检验字符是否为日期格式：
	 * yyyy-MM-dd 
	 * yyyy-MM-dd hh:mm:ss
	 * @param date
	 * @return
	 */
	public static boolean checkDate(String date) {
		String eL = "^((\\d{2}(([02468][048])|([13579][26]))[\\-\\/\\s]?((((0?[13578])|(1[02]))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])))))|(\\d{2}(([02468][1235679])|([13579][01345789]))[\\-\\/\\s]?((((0?[13578])|(1[02]))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[\\-\\/\\s]?((0?[1-9])|(1[0-9])|(2[0-8]))))))(\\s(((0?[0-9])|([1][0-9])|([2][0-3]))\\:([0-5]?[0-9])((\\s)|(\\:([0-5]?[0-9])))))?$";
		Pattern p = Pattern.compile(eL);
		Matcher m = p.matcher(date);
		boolean b = m.matches();
		return b;
	}

	/**
	 * 转换泛型对象
	 * @param <T>
	 * @param claszz
	 * @param source
	 * @return
	 * @throws ParseException
	 */
	@SuppressWarnings("unchecked")
	private static <T> T convert(Class<T> claszz, String source) throws ParseException {
		T date = (T) source;
		if (claszz == Date.class) {
			if (checkDate(source)) {
				DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
				date = (T) sdf.parse(source);
			} else {
				date = null;
			}
		}
		return date;
	}

}
